using System;

namespace RSEngine.Math
{
    /// <summary>
    /// Representation of a ray in space, ie a line with an origin and direction.
    /// </summary>
    sealed public class Ray
    {

        #region Variables

        public Vector3 Origin;
        public Vector3 Direction;

        public float Min = float.Epsilon;
        public float Max = float.PositiveInfinity;

        #endregion

        #region Constructors

        public Ray()
        {
            Origin = Vector3.Zero;
            Direction = Vector3.UnitZ;
        }

        public Ray(Vector3 origin, Vector3 direction)
        {
            this.Origin = origin;
            this.Direction = direction;
        }

        #endregion

        #region GetPoint / GetPosition

        /// <summary>
        /// Gets the position of a point t units along the ray.
        /// </summary>
        public Vector3 GetPoint(float t)
        {
            return Origin + (Direction * t);
        }

        /// <summary>
        /// Gets the position of a point t units along the ray.
        /// </summary>
        public Vector3 this[float t]
        {
            get
            {
                return Origin + (Direction * t);
            }
        }

        #endregion

        #region IsInside

        /// <summary>
        /// Checks to see if the specified distance falls within the valid range on
        /// this ray. This should always be used before an intersection with the ray
        /// is detected.
        /// </summary>
        /// <param name="t">distance to be tested</param>
        /// <returns>Returns true if t falls between the minimum and maximum distance of this ray</returns>
        public bool IsInside(float t)
        {
            return (Min < t) && (t < Max);
        }

        #endregion

        #region Operators

        /// <summary>
        /// Gets the position of a point t units along the ray.
        /// </summary>
        public static Vector3 operator *(Ray ray, float t)
        {
            return ray.Origin + (ray.Direction * t);
        }

        public static bool operator ==(Ray left, Ray right)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(left, right))
                return true;

            // If one is null, but not both, return false.
            if (((object)left == null) || ((object)right == null))
                return false;

            return left.Direction == right.Direction && left.Origin == right.Origin;
        }

        public static bool operator !=(Ray left, Ray right)
        {
            return !(left == right);
        }

        #endregion

        #region Misc

        public override bool Equals(object obj)
        {
            return obj is Ray && this == (Ray)obj;
        }

        public override int GetHashCode()
        {
            return Direction.GetHashCode() ^ Origin.GetHashCode();
        }

        #endregion

    }
}
